{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Working with Order Book Data: NASDAQ ITCH"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The primary source of market data is the order book, which is continuously updated in real-time throughout the day to reflect all trading activity. Exchanges typically offer this data as a real-time service and may provide some historical data for free.\n",
    "\n",
    "The trading activity is reflected in numerous messages about trade orders sent by market participants. These messages typically conform to the electronic Financial Information eXchange (FIX) communications protocol for real-time exchange of securities transactions and market data or a native exchange protocol. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Background"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The FIX Protocol"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just like SWIFT is the message protocol for back-office (example, for trade-settlement) messaging, the [FIX protocol](https://www.fixtrading.org/standards/) is the de facto messaging standard for communication before and during, trade execution between exchanges, banks, brokers, clearing firms, and other market participants. Fidelity Investments and Salomon Brothers introduced FIX in 1992 to facilitate electronic communication between broker-dealers and institutional clients who by then exchanged information over the phone.\n",
    "\n",
    "It became popular in global equity markets before expanding into foreign exchange, fixed income and derivatives markets, and further into post-trade to support straight-through processing. Exchanges provide access to FIX messages as a real-time data feed that is parsed by algorithmic traders to track market activity and, for example, identify the footprint of market participants and anticipate their next move. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Nasdaq TotalView-ITCH Order Book data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While FIX has a dominant large market share, exchanges also offer native protocols. The Nasdaq offers a [TotalView ITCH direct data-feed protocol](http://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHspecification.pdf) that allows subscribers to track \n",
    "individual orders for equity instruments from placement to execution or cancellation.\n",
    "\n",
    "As a result, it allows for the reconstruction of the order book that keeps track of the list of active-limit buy and sell orders for a specific security or financial instrument. The order book reveals the market depth throughout the day by listing the number of shares being bid or offered at each price point. It may also identify the market participant responsible for specific buy and sell orders unless it is placed anonymously. Market depth is a key indicator of liquidity and the potential price impact of sizable market orders. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ITCH v5.0 specification declares over 20 message types related to system events, stock characteristics, the placement and modification of limit orders, and trade execution. It also contains information about the net order imbalance before the open and closing cross."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-27T15:17:02.911889Z",
     "start_time": "2020-03-27T15:17:02.895148Z"
    }
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import gzip\n",
    "import shutil\n",
    "from struct import unpack\n",
    "from collections import namedtuple, Counter, defaultdict\n",
    "from pathlib import Path\n",
    "from urllib.request import urlretrieve\n",
    "from urllib.parse import urljoin\n",
    "from datetime import timedelta\n",
    "from time import time\n",
    "\n",
    "import pandas as pd\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib.ticker import FuncFormatter\n",
    "import seaborn as sns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-27T15:17:08.818400Z",
     "start_time": "2020-03-27T15:17:08.813922Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "sns.set_style('whitegrid')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "def format_time(t):\n",
    "    \"\"\"Return a formatted time string 'HH:MM:SS\n",
    "    based on a numeric time() value\"\"\"\n",
    "    m, s = divmod(t, 60)\n",
    "    h, m = divmod(m, 60)\n",
    "    return f'{h:0>2.0f}:{m:0>2.0f}:{s:0>5.2f}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Get NASDAQ ITCH Data from FTP Server"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Nasdaq offers [samples](ftp://emi.nasdaq.com/ITCH/) of daily binary files for several months. \n",
    "\n",
    "We are now going to illustrates how to parse a sample file of ITCH messages and reconstruct both the executed trades and the order book for any given tick."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The data is fairly large and running the entire example can take a lot of time and require substantial memory (16GB+). Also, the sample file used in this example may no longer be available because NASDAQ occasionaly updates the sample files."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following table shows the frequency of the most common message types for the sample file date March 29, 2018:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Name                    | Offset  | Length  | Value      | Notes                                                                                |\n",
    "|-------------------------|---------|---------|------------|--------------------------------------------------------------------------------------|\n",
    "| Message Type            | 0       | 1       | S          | System Event Message                                                                 |\n",
    "| Stock Locate            | 1       | 2       | Integer    | Always 0                                                                             |\n",
    "| Tracking Number         | 3       | 2       | Integer    | Nasdaq internal tracking number                                                      |\n",
    "| Timestamp               | 5       | 6       | Integer    | Nanoseconds since midnight                                                           |\n",
    "| Order Reference Number  | 11      | 8       | Integer    | The unique reference number assigned to the new order at the time of receipt.        |\n",
    "| Buy/Sell Indicator      | 19      | 1       | Alpha      | The type of order being added. B = Buy Order. S = Sell Order.                        |\n",
    "| Shares                  | 20      | 4       | Integer    | The total number of shares associated with the order being added to the book.        |\n",
    "| Stock                   | 24      | 8       | Alpha      | Stock symbol, right padded with spaces                                               |\n",
    "| Price                   | 32      | 4       | Price (4)  | The display price of the new order. Refer to Data Types for field processing notes.  |\n",
    "| Attribution             | 36      | 4       | Alpha      | Nasdaq Market participant identifier associated with the entered order               |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "### Set Data paths"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will store the download in a `data` subdirectory and convert the result to `hdf` format (discussed in the last section of chapter 2)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-27T15:17:33.771126Z",
     "start_time": "2020-03-27T15:17:33.767761Z"
    }
   },
   "outputs": [],
   "source": [
    "data_path = Path('data') # set to e.g. external harddrive\n",
    "itch_store = str(data_path / 'itch.h5')\n",
    "order_book_store = data_path / 'order_book.h5'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can find several sample files on the [NASDAQ ftp server](ftp://emi.nasdaq.com/ITCH/).\n",
    "\n",
    "The FTP address, filename and corresponding date used in this example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:16:30.190820Z",
     "start_time": "2018-12-25T17:16:30.189063Z"
    }
   },
   "outputs": [],
   "source": [
    "FTP_URL = 'ftp://emi.nasdaq.com/ITCH/'\n",
    "SOURCE_FILE = '10302019.NASDAQ_ITCH50.gz'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Download & unzip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:17:03.453672Z",
     "start_time": "2018-12-25T17:17:03.444627Z"
    }
   },
   "outputs": [],
   "source": [
    "def may_be_download(url):\n",
    "    \"\"\"Download & unzip ITCH data if not yet available\"\"\"\n",
    "    if not data_path.exists():\n",
    "        print('Creating directory')\n",
    "        data_path.mkdir()\n",
    "    else: \n",
    "        print('Directory exists')\n",
    "\n",
    "    filename = data_path / url.split('/')[-1]        \n",
    "    if not filename.exists():\n",
    "        print('Downloading...', url)\n",
    "        urlretrieve(url, filename)\n",
    "    else: \n",
    "        print('File exists')        \n",
    "\n",
    "    unzipped = data_path / (filename.stem + '.bin')\n",
    "    if not unzipped.exists():\n",
    "        print('Unzipping to', unzipped)\n",
    "        with gzip.open(str(filename), 'rb') as f_in:\n",
    "            with open(unzipped, 'wb') as f_out:\n",
    "                shutil.copyfileobj(f_in, f_out)\n",
    "    else: \n",
    "        print('File already unpacked')\n",
    "    return unzipped"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This will download 5.1GB data that unzips to 12.9GB."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T19:06:08.577453Z",
     "start_time": "2018-12-25T19:06:08.570117Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Directory exists\n",
      "File exists\n",
      "Unzipping to data/10302019.NASDAQ_ITCH50.bin\n"
     ]
    }
   ],
   "source": [
    "file_name = may_be_download(urljoin(FTP_URL, SOURCE_FILE))\n",
    "date = file_name.name.split('.')[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ITCH Format Settings"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The `struct` module for binary data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ITCH tick data comes in binary format. Python provides the `struct` module (see [docs])(https://docs.python.org/3/library/struct.html) to parse binary data using format strings that identify the message elements by indicating length and type of the various components of the byte string as laid out in the specification."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "From the docs:\n",
    "\n",
    "> This module performs conversions between Python values and C structs represented as Python bytes objects. This can be used in handling binary data stored in files or from network connections, among other sources. It uses Format Strings as compact descriptions of the layout of the C structs and the intended conversion to/from Python values."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's walk through the critical steps to parse the trading messages and reconstruct the order book:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "### Defining format strings"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "The parser uses format strings according to the following formats dictionaries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:23:09.630040Z",
     "start_time": "2018-12-25T17:23:09.628023Z"
    }
   },
   "outputs": [],
   "source": [
    "event_codes = {'O': 'Start of Messages',\n",
    "               'S': 'Start of System Hours',\n",
    "               'Q': 'Start of Market Hours',\n",
    "               'M': 'End of Market Hours',\n",
    "               'E': 'End of System Hours',\n",
    "               'C': 'End of Messages'}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:23:09.841397Z",
     "start_time": "2018-12-25T17:23:09.835447Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "encoding = {'primary_market_maker': {'Y': 1, 'N': 0},\n",
    "            'printable'           : {'Y': 1, 'N': 0},\n",
    "            'buy_sell_indicator'  : {'B': 1, 'S': -1},\n",
    "            'cross_type'          : {'O': 0, 'C': 1, 'H': 2},\n",
    "            'imbalance_direction' : {'B': 0, 'S': 1, 'N': 0, 'O': -1}}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:23:09.999064Z",
     "start_time": "2018-12-25T17:23:09.992338Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "formats = {\n",
    "    ('integer', 2): 'H', # int of length 2 => format string 'H'\n",
    "    ('integer', 4): 'I',\n",
    "    ('integer', 6): '6s', # int of length 6 => parse as string, convert later\n",
    "\n",
    "    ('integer', 8): 'Q',\n",
    "    ('alpha', 1)  : 's',\n",
    "    ('alpha', 2)  : '2s',\n",
    "    ('alpha', 4)  : '4s',\n",
    "    ('alpha', 8)  : '8s',\n",
    "    ('price_4', 4): 'I',\n",
    "    ('price_8', 8): 'Q',\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create message specs for binary data parser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ITCH parser relies on message specifications that we create in the following steps."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "#### Load Message Types"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "The file `message_types.xlxs` contains the message type specs as laid out in the [documentation](https://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHSpecification.pdf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T18:36:19.798092Z",
     "start_time": "2018-12-25T18:36:19.783135Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "message_data = (pd.read_excel('message_types.xlsx',\n",
    "                              sheet_name='messages')\n",
    "                .sort_values('id')\n",
    "                .drop('id', axis=1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Name</th>\n",
       "      <th>Offset</th>\n",
       "      <th>Length</th>\n",
       "      <th>Value</th>\n",
       "      <th>Notes</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Message Type</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>S</td>\n",
       "      <td>System Event Message</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Stock Locate</td>\n",
       "      <td>1</td>\n",
       "      <td>2</td>\n",
       "      <td>Integer</td>\n",
       "      <td>Always 0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Tracking Number</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>Integer</td>\n",
       "      <td>Nasdaq internal tracking number</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Timestamp</td>\n",
       "      <td>5</td>\n",
       "      <td>6</td>\n",
       "      <td>Integer</td>\n",
       "      <td>Nanoseconds since midnight</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Event Code</td>\n",
       "      <td>11</td>\n",
       "      <td>1</td>\n",
       "      <td>Alpha</td>\n",
       "      <td>See System Event Codes below</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "              Name   Offset   Length     Value   \\\n",
       "0     Message Type         0        1         S   \n",
       "1     Stock Locate         1        2  Integer    \n",
       "2  Tracking Number         3        2  Integer    \n",
       "3        Timestamp         5        6  Integer    \n",
       "4       Event Code        11        1    Alpha    \n",
       "\n",
       "                             Notes   \n",
       "0             System Event Message   \n",
       "1                         Always 0   \n",
       "2  Nasdaq internal tracking number   \n",
       "3       Nanoseconds since midnight   \n",
       "4     See System Event Codes below   "
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "message_data.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "#### Basic Cleaning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "The function `clean_message_types()` just runs a few basic string cleaning steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T18:36:18.635039Z",
     "start_time": "2018-12-25T18:36:18.630282Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "def clean_message_types(df):\n",
    "    df.columns = [c.lower().strip() for c in df.columns]\n",
    "    df.value = df.value.str.strip()\n",
    "    df.name = (df.name\n",
    "               .str.strip() # remove whitespace\n",
    "               .str.lower()\n",
    "               .str.replace(' ', '_')\n",
    "               .str.replace('-', '_')\n",
    "               .str.replace('/', '_'))\n",
    "    df.notes = df.notes.str.strip()\n",
    "    df['message_type'] = df.loc[df.name == 'message_type', 'value']\n",
    "    return df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "message_types = clean_message_types(message_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "#### Get Message Labels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We extract message type codes and names so we can later make the results more readable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T18:36:22.741964Z",
     "start_time": "2018-12-25T18:36:22.734576Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>message_type</th>\n",
       "      <th>name</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>S</td>\n",
       "      <td>system_event</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>R</td>\n",
       "      <td>stock_directory</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>23</th>\n",
       "      <td>H</td>\n",
       "      <td>stock_trading_action</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>31</th>\n",
       "      <td>Y</td>\n",
       "      <td>reg_sho_short_sale_price_test_restricted_indic...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>37</th>\n",
       "      <td>L</td>\n",
       "      <td>market_participant_position</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   message_type                                               name\n",
       "0             S                                       system_event\n",
       "5             R                                    stock_directory\n",
       "23            H                               stock_trading_action\n",
       "31            Y  reg_sho_short_sale_price_test_restricted_indic...\n",
       "37            L                        market_participant_position"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "message_labels = (message_types.loc[:, ['message_type', 'notes']]\n",
    "                  .dropna()\n",
    "                  .rename(columns={'notes': 'name'}))\n",
    "message_labels.name = (message_labels.name\n",
    "                       .str.lower()\n",
    "                       .str.replace('message', '')\n",
    "                       .str.replace('.', '')\n",
    "                       .str.strip().str.replace(' ', '_'))\n",
    "# message_labels.to_csv('message_labels.csv', index=False)\n",
    "message_labels.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "### Finalize specification details"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Each message consists of several fields that are defined by offset, length and type of value. The `struct` module will use this format information to parse the binary source data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T18:36:57.493706Z",
     "start_time": "2018-12-25T18:36:56.614416Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "Int64Index: 152 entries, 1 to 172\n",
      "Data columns (total 6 columns):\n",
      " #   Column        Non-Null Count  Dtype \n",
      "---  ------        --------------  ----- \n",
      " 0   name          152 non-null    object\n",
      " 1   offset        152 non-null    int64 \n",
      " 2   length        152 non-null    int64 \n",
      " 3   value         152 non-null    object\n",
      " 4   notes         152 non-null    object\n",
      " 5   message_type  152 non-null    object\n",
      "dtypes: int64(2), object(4)\n",
      "memory usage: 8.3+ KB\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/stefan/.pyenv/versions/miniconda3-latest/envs/ml4t/lib/python3.7/site-packages/pandas/core/generic.py:5303: SettingWithCopyWarning: \n",
      "A value is trying to be set on a copy of a slice from a DataFrame.\n",
      "Try using .loc[row_indexer,col_indexer] = value instead\n",
      "\n",
      "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
      "  self[name] = value\n"
     ]
    }
   ],
   "source": [
    "message_types.message_type = message_types.message_type.ffill()\n",
    "message_types = message_types[message_types.name != 'message_type']\n",
    "message_types.value = (message_types.value\n",
    "                       .str.lower()\n",
    "                       .str.replace(' ', '_')\n",
    "                       .str.replace('(', '')\n",
    "                       .str.replace(')', ''))\n",
    "message_types.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>name</th>\n",
       "      <th>offset</th>\n",
       "      <th>length</th>\n",
       "      <th>value</th>\n",
       "      <th>notes</th>\n",
       "      <th>message_type</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>stock_locate</td>\n",
       "      <td>1</td>\n",
       "      <td>2</td>\n",
       "      <td>integer</td>\n",
       "      <td>Always 0</td>\n",
       "      <td>S</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>tracking_number</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>integer</td>\n",
       "      <td>Nasdaq internal tracking number</td>\n",
       "      <td>S</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>timestamp</td>\n",
       "      <td>5</td>\n",
       "      <td>6</td>\n",
       "      <td>integer</td>\n",
       "      <td>Nanoseconds since midnight</td>\n",
       "      <td>S</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>event_code</td>\n",
       "      <td>11</td>\n",
       "      <td>1</td>\n",
       "      <td>alpha</td>\n",
       "      <td>See System Event Codes below</td>\n",
       "      <td>S</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>stock_locate</td>\n",
       "      <td>1</td>\n",
       "      <td>2</td>\n",
       "      <td>integer</td>\n",
       "      <td>Locate Code uniquely assigned to the security ...</td>\n",
       "      <td>R</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "              name  offset  length    value  \\\n",
       "1     stock_locate       1       2  integer   \n",
       "2  tracking_number       3       2  integer   \n",
       "3        timestamp       5       6  integer   \n",
       "4       event_code      11       1    alpha   \n",
       "6     stock_locate       1       2  integer   \n",
       "\n",
       "                                               notes message_type  \n",
       "1                                           Always 0            S  \n",
       "2                    Nasdaq internal tracking number            S  \n",
       "3                         Nanoseconds since midnight            S  \n",
       "4                       See System Event Codes below            S  \n",
       "6  Locate Code uniquely assigned to the security ...            R  "
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "message_types.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Optionally, persist/reload from file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "message_types.to_csv('message_types.csv', index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "message_types = pd.read_csv('message_types.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The parser translates the message specs into format strings and `namedtuples` that capture the message content. First, we create `(type, length)` formatting tuples from ITCH specs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:29:25.322056Z",
     "start_time": "2018-12-25T17:29:25.291911Z"
    }
   },
   "outputs": [],
   "source": [
    "message_types.loc[:, 'formats'] = (message_types[['value', 'length']]\n",
    "                            .apply(tuple, axis=1).map(formats))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then, we extract formatting details for alphanumerical fields"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:29:27.058965Z",
     "start_time": "2018-12-25T17:29:27.044699Z"
    }
   },
   "outputs": [],
   "source": [
    "alpha_fields = message_types[message_types.value == 'alpha'].set_index('name')\n",
    "alpha_msgs = alpha_fields.groupby('message_type')\n",
    "alpha_formats = {k: v.to_dict() for k, v in alpha_msgs.formats}\n",
    "alpha_length = {k: v.add(5).to_dict() for k, v in alpha_msgs.length}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "We generate message classes as named tuples and format strings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:29:27.755034Z",
     "start_time": "2018-12-25T17:29:27.722828Z"
    }
   },
   "outputs": [],
   "source": [
    "message_fields, fstring = {}, {}\n",
    "for t, message in message_types.groupby('message_type'):\n",
    "    message_fields[t] = namedtuple(typename=t, field_names=message.name.tolist())\n",
    "    fstring[t] = '>' + ''.join(message.formats.tolist())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "Index: 45 entries, event_code to price_variation_indicator\n",
      "Data columns (total 6 columns):\n",
      " #   Column        Non-Null Count  Dtype \n",
      "---  ------        --------------  ----- \n",
      " 0   offset        45 non-null     int64 \n",
      " 1   length        45 non-null     int64 \n",
      " 2   value         45 non-null     object\n",
      " 3   notes         45 non-null     object\n",
      " 4   message_type  45 non-null     object\n",
      " 5   formats       45 non-null     object\n",
      "dtypes: int64(2), object(4)\n",
      "memory usage: 2.5+ KB\n"
     ]
    }
   ],
   "source": [
    "alpha_fields.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>offset</th>\n",
       "      <th>length</th>\n",
       "      <th>value</th>\n",
       "      <th>notes</th>\n",
       "      <th>message_type</th>\n",
       "      <th>formats</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>name</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>event_code</th>\n",
       "      <td>11</td>\n",
       "      <td>1</td>\n",
       "      <td>alpha</td>\n",
       "      <td>See System Event Codes below</td>\n",
       "      <td>S</td>\n",
       "      <td>s</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>stock</th>\n",
       "      <td>11</td>\n",
       "      <td>8</td>\n",
       "      <td>alpha</td>\n",
       "      <td>Denotes the security symbol for the issue in t...</td>\n",
       "      <td>R</td>\n",
       "      <td>8s</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>market_category</th>\n",
       "      <td>19</td>\n",
       "      <td>1</td>\n",
       "      <td>alpha</td>\n",
       "      <td>Indicates Listing market or listing market tie...</td>\n",
       "      <td>R</td>\n",
       "      <td>s</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>financial_status_indicator</th>\n",
       "      <td>20</td>\n",
       "      <td>1</td>\n",
       "      <td>alpha</td>\n",
       "      <td>For Nasdaq listed issues, this field indicates...</td>\n",
       "      <td>R</td>\n",
       "      <td>s</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>round_lots_only</th>\n",
       "      <td>25</td>\n",
       "      <td>1</td>\n",
       "      <td>alpha</td>\n",
       "      <td>Indicates if Nasdaq system limits order entry ...</td>\n",
       "      <td>R</td>\n",
       "      <td>s</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            offset  length  value  \\\n",
       "name                                                \n",
       "event_code                      11       1  alpha   \n",
       "stock                           11       8  alpha   \n",
       "market_category                 19       1  alpha   \n",
       "financial_status_indicator      20       1  alpha   \n",
       "round_lots_only                 25       1  alpha   \n",
       "\n",
       "                                                                        notes  \\\n",
       "name                                                                            \n",
       "event_code                                       See System Event Codes below   \n",
       "stock                       Denotes the security symbol for the issue in t...   \n",
       "market_category             Indicates Listing market or listing market tie...   \n",
       "financial_status_indicator  For Nasdaq listed issues, this field indicates...   \n",
       "round_lots_only             Indicates if Nasdaq system limits order entry ...   \n",
       "\n",
       "                           message_type formats  \n",
       "name                                             \n",
       "event_code                            S       s  \n",
       "stock                                 R      8s  \n",
       "market_category                       R       s  \n",
       "financial_status_indicator            R       s  \n",
       "round_lots_only                       R       s  "
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "alpha_fields.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fields of `alpha` type (alphanumeric) require post-processing as defined in the `format_alpha` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:29:28.145192Z",
     "start_time": "2018-12-25T17:29:28.131796Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "def format_alpha(mtype, data):\n",
    "    \"\"\"Process byte strings of type alpha\"\"\"\n",
    "\n",
    "    for col in alpha_formats.get(mtype).keys():\n",
    "        if mtype != 'R' and col == 'stock':\n",
    "            data = data.drop(col, axis=1)\n",
    "            continue\n",
    "        data.loc[:, col] = data.loc[:, col].str.decode(\"utf-8\").str.strip()\n",
    "        if encoding.get(col):\n",
    "            data.loc[:, col] = data.loc[:, col].map(encoding.get(col))\n",
    "    return data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Process Binary Message Data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "The binary file for a single day contains over 350,000,000 messages worth over 12 GB."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:29:45.620911Z",
     "start_time": "2018-12-25T17:29:45.606916Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "def store_messages(m):\n",
    "    \"\"\"Handle occasional storing of all messages\"\"\"\n",
    "    with pd.HDFStore(itch_store) as store:\n",
    "        for mtype, data in m.items():\n",
    "            # convert to DataFrame\n",
    "            data = pd.DataFrame(data)\n",
    "\n",
    "            # parse timestamp info\n",
    "            data.timestamp = data.timestamp.apply(int.from_bytes, byteorder='big')\n",
    "            data.timestamp = pd.to_timedelta(data.timestamp)\n",
    "\n",
    "            # apply alpha formatting\n",
    "            if mtype in alpha_formats.keys():\n",
    "                data = format_alpha(mtype, data)\n",
    "\n",
    "            s = alpha_length.get(mtype)\n",
    "            if s:\n",
    "                s = {c: s.get(c) for c in data.columns}\n",
    "            dc = ['stock_locate']\n",
    "            if m == 'R':\n",
    "                dc.append('stock')\n",
    "            try:\n",
    "                store.append(mtype,\n",
    "                         data,\n",
    "                         format='t',\n",
    "                         min_itemsize=s,\n",
    "                         data_columns=dc)\n",
    "            except Exception as e:\n",
    "                print(e)\n",
    "                print(mtype)\n",
    "                print(data.info())\n",
    "                print(pd.Series(list(m.keys())).value_counts())\n",
    "                data.to_csv('data.csv', index=False)\n",
    "                return 1\n",
    "    return 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:29:45.638871Z",
     "start_time": "2018-12-25T17:29:45.623938Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "messages = defaultdict(list)\n",
    "message_count = 0\n",
    "message_type_counter = Counter()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "The script appends the parsed result iteratively to a file in the fast HDF5 format using the `store_messages()` function we just defined to avoid memory constraints (see last section in chapter 2 for more on this format)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following code processes the binary file and produces the parsed orders stored by message type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T17:59:34.870288Z",
     "start_time": "2018-12-25T17:29:45.640518Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      " Start of Messages\n",
      "\t3:02:31.647283\t0\n",
      "\n",
      " Start of System Hours\n",
      "\t4:00:00.000203\t241,258\n",
      "\n",
      " Start of Market Hours\n",
      "\t9:30:00.000070\t9,559,279\n",
      "\t9:44:09.234742\t25,000,000\t0:02:05.464529\n",
      "\t10:07:45.145293\t50,000,000\t0:07:11.329473\n",
      "\t10:39:56.236836\t75,000,000\t0:11:45.350465\n",
      "\t11:18:09.635432\t100,000,000\t0:16:30.643469\n",
      "\t11:58:35.348562\t125,000,000\t0:21:17.344847\n",
      "\t12:44:20.614655\t150,000,000\t0:25:58.004012\n",
      "\t13:41:03.747497\t175,000,000\t0:30:33.274380\n",
      "\t14:18:44.524342\t200,000,000\t0:35:17.456962\n",
      "\t14:49:19.384369\t225,000,000\t0:39:54.900770\n",
      "\t15:19:40.719266\t250,000,000\t0:45:06.775091\n",
      "\t15:50:23.011120\t275,000,000\t0:49:43.263130\n",
      "\n",
      " End of Market Hours\n",
      "\t16:00:00.000087\t290,920,164\n",
      "\n",
      " End of System Hours\n",
      "\t20:00:00.000021\t293,944,863\n",
      "\n",
      " End of Messages\n",
      "\t20:05:00.000062\t293,989,078\n",
      "0:56:05.417711\n"
     ]
    }
   ],
   "source": [
    "start = time()\n",
    "with file_name.open('rb') as data:\n",
    "    while True:\n",
    "\n",
    "        # determine message size in bytes\n",
    "        message_size = int.from_bytes(data.read(2), byteorder='big', signed=False)\n",
    "        \n",
    "        # get message type by reading first byte\n",
    "        message_type = data.read(1).decode('ascii')        \n",
    "        message_type_counter.update([message_type])\n",
    "\n",
    "        # read & store message\n",
    "        record = data.read(message_size - 1)\n",
    "        message = message_fields[message_type]._make(unpack(fstring[message_type], record))\n",
    "        messages[message_type].append(message)\n",
    "        \n",
    "        # deal with system events\n",
    "        if message_type == 'S':\n",
    "            seconds = int.from_bytes(message.timestamp, byteorder='big') * 1e-9\n",
    "            print('\\n', event_codes.get(message.event_code.decode('ascii'), 'Error'))\n",
    "            print(f'\\t{format_time(seconds)}\\t{message_count:12,.0f}')\n",
    "            if message.event_code.decode('ascii') == 'C':\n",
    "                store_messages(messages)\n",
    "                break\n",
    "        message_count += 1\n",
    "\n",
    "        if message_count % 2.5e7 == 0:\n",
    "            seconds = int.from_bytes(message.timestamp, byteorder='big') * 1e-9\n",
    "            d = format_time(time() - start)\n",
    "            print(f'\\t{format_time(seconds)}\\t{message_count:12,.0f}\\t{d}')\n",
    "            res = store_messages(messages)\n",
    "            if res == 1:\n",
    "                print(pd.Series(dict(message_type_counter)).sort_values())\n",
    "                break\n",
    "            messages.clear()\n",
    "\n",
    "print('Duration:', format_time(time() - start))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Summarize Trading Day"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "### Trading Message Frequency"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T18:38:03.560168Z",
     "start_time": "2018-12-25T18:38:03.551636Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Message Type</th>\n",
       "      <th># Trades</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>A</th>\n",
       "      <td>add_order_no_mpid_attribution</td>\n",
       "      <td>127214649</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>D</th>\n",
       "      <td>order_delete</td>\n",
       "      <td>123296742</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>U</th>\n",
       "      <td>order_replace</td>\n",
       "      <td>25513651</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>E</th>\n",
       "      <td>order_executed</td>\n",
       "      <td>7316703</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>I</th>\n",
       "      <td>noii</td>\n",
       "      <td>3740140</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>X</th>\n",
       "      <td>order_cancel</td>\n",
       "      <td>3568735</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>P</th>\n",
       "      <td>trade</td>\n",
       "      <td>1525363</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>F</th>\n",
       "      <td>add_order_mpid_attribution</td>\n",
       "      <td>1423908</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>L</th>\n",
       "      <td>market_participant_position</td>\n",
       "      <td>214865</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C</th>\n",
       "      <td>order_executed_with_price</td>\n",
       "      <td>129729</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Q</th>\n",
       "      <td>cross_trade</td>\n",
       "      <td>17775</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Y</th>\n",
       "      <td>reg_sho_short_sale_price_test_restricted_indic...</td>\n",
       "      <td>9025</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>H</th>\n",
       "      <td>stock_trading_action</td>\n",
       "      <td>8897</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>R</th>\n",
       "      <td>stock_directory</td>\n",
       "      <td>8887</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>S</th>\n",
       "      <td>system_event</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>J</th>\n",
       "      <td>luld_auction_collar</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>V</th>\n",
       "      <td>market_wide_circuit_breaker_decline_level</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>B</th>\n",
       "      <td>broken_trade</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                        Message Type   # Trades\n",
       "A                      add_order_no_mpid_attribution  127214649\n",
       "D                                       order_delete  123296742\n",
       "U                                      order_replace   25513651\n",
       "E                                     order_executed    7316703\n",
       "I                                               noii    3740140\n",
       "X                                       order_cancel    3568735\n",
       "P                                              trade    1525363\n",
       "F                         add_order_mpid_attribution    1423908\n",
       "L                        market_participant_position     214865\n",
       "C                          order_executed_with_price     129729\n",
       "Q                                        cross_trade      17775\n",
       "Y  reg_sho_short_sale_price_test_restricted_indic...       9025\n",
       "H                               stock_trading_action       8897\n",
       "R                                    stock_directory       8887\n",
       "S                                       system_event          6\n",
       "J                                luld_auction_collar          2\n",
       "V          market_wide_circuit_breaker_decline_level          1\n",
       "B                                       broken_trade          1"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counter = pd.Series(message_type_counter).to_frame('# Trades')\n",
    "counter['Message Type'] = counter.index.map(message_labels.set_index('message_type').name.to_dict())\n",
    "counter = counter[['Message Type', '# Trades']].sort_values('# Trades', ascending=False)\n",
    "counter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-12-25T18:38:05.819908Z",
     "start_time": "2018-12-25T18:38:05.810713Z"
    }
   },
   "outputs": [],
   "source": [
    "with pd.HDFStore(itch_store) as store:\n",
    "    store.put('summary', counter)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "### Top Equities by Traded Value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "with pd.HDFStore(itch_store) as store:\n",
    "    stocks = store['R'].loc[:, ['stock_locate', 'stock']]\n",
    "    trades = store['P'].append(store['Q'].rename(columns={'cross_price': 'price'}), sort=False).merge(stocks)\n",
    "\n",
    "trades['value'] = trades.shares.mul(trades.price)\n",
    "trades['value_share'] = trades.value.div(trades.value.sum())\n",
    "\n",
    "trade_summary = trades.groupby('stock').value_share.sum().sort_values(ascending=False)\n",
    "trade_summary.iloc[:50].plot.bar(figsize=(14, 6), color='darkblue', title='Share of Traded Value')\n",
    "\n",
    "plt.gca().yaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y)))\n",
    "sns.despine()\n",
    "plt.tight_layout()"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-27T15:19:06.905421Z",
     "start_time": "2020-03-27T15:19:05.149409Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA+gAAAGoCAYAAADVZM+hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+j8jraAAAgAElEQVR4nOzdeXRU5eH/8c+EQBIYGhRBQUDrBoltWVyx4oIKWIOYEnZCKwIVVAqIIBFiDAgICCFAhNIiVkEjUdFCLS2KpiASqMZ+kURBy77LGsg+8/uDX+YkkGVm8gAPmffrHM6Z3Jn7mWdCZvnc5947Drfb7RYAAAAAALiogi72AAAAAAAAAAUdAAAAAAArUNABAAAAALAABR0AAAAAAAtQ0AEAAAAAsAAFHQAAAAAAC1DQAQD4/zIzMxUbG6uuXbsqKipKgwYN0tatWyVJGzZsUFRU1EUe4RkvvviiOnbsqFmzZnmWbdu2Td26dVO3bt1033336ZZbbvH8vHjxYr/v6y9/+Yuef/55n9Y5cuSIWrZsec7yuXPn6oknnjhn+bfffqv27duroKCg3DybfvcAAJxPwRd7AAAA2KCgoEB/+MMftGjRIt18882SpA8//FCDBw/WJ598cpFHV1Zqaqo+++wzXXXVVZ5lN9xwgz788ENJ0vvvv69Vq1ZpwYIFF2uI5erZs6cWLFigffv2qUmTJp7lqamp6tGjh+rUqXMRRwcAwMVHQQcAQFJubq5Onjyp06dPe5Y9+uijcjqdKi4uliSdPn1aI0eO1I8//qj8/HxNmjRJt956q/73v/8pMTFRp06d0qFDh9SqVSslJSUpJCREv/jFL/TAAw8oOztbM2bMUN26dfXyyy/r2LFjKi4uVmxsrGJiYs4Zz9atW5WYmKhjx47J4XBo4MCBeuyxx9S3b1+53W4NHjxYL774om699VavHt/Z4/juu++UmpqqwsJCHT9+XIMHD1bfvn1VWFioSZMm6YsvvlDDhg3VsGFD1a9fX5J08uRJvfzyy/r+++9VWFio9u3ba8yYMQoODtY///lPzZo1S2FhYfrFL35R7hgaN26sjh076v3339dTTz0lSTp16pQ+/vhjffjhh1qzZo0WLFiggoICHTlyRI899phGjBhRJuP555/XjTfe6JmJL/3zgQMHlJiYqH379qmwsFCPPPKInnzySa9+PwAA2ICCDgCApPDwcD333HMaNGiQrrjiCrVr10533HGHHnnkEc/M7v79+zVr1iy1bt1aixcv1pw5c/TGG2/o3Xff1WOPPaZu3bqpsLBQv/3tb/XZZ5+pc+fOKiws1P3336/Zs2erqKhI3bp107Rp03TzzTfr5MmT6tWrl2644Qa1adPGM5aioiINHTpUY8aMUadOnXTgwAH16NFD11xzjZYuXaqWLVvqjTfe0OWXX+714ys9jlOnTmnSpEn605/+pMsuu0yZmZl6/PHH1bdvXy1dulTbt2/XypUrVVRUpP79+3sK+uTJk3XzzTdr6tSpKi4u1vPPP6/XX39d0dHRiouL0zvvvKMbbrih0pn7vn37Ki4uTsOGDZPD4dDKlSt1++23q0mTJho7dqymTp2qa6+9VgcOHND999+vAQMGeP0Yn3vuOf3+979Xx44dlZ+fr8GDB6tFixb6zW9+43UGAAAXEwUdAID/7/HHH1ePHj20ceNGbdy4UQsXLtTChQuVlpYmSWrevLlat24tSWrVqpXee+89SWeK4bp167Rw4UJt375dBw8eLDMTXzLLvX37du3cuVNxcXGe6/Ly8rRly5YyBX379u3Kz89Xp06dJElXXnmlOnXqpH//+99q27at34+vZBz16tXT/Pnz9fnnn2v79u3Kzs72jHf9+vWKiopSnTp1VKdOHXXt2lXfffedJOmzzz7T//3f/3l+H3l5eZKk//znP7rpppt0ww03SJJ69eqlmTNnljuGO+64Q2FhYfryyy/Vvn17paamavTo0XI4HJo/f74+++wzrVixQj/88IPcbrdyc3O9emynT5/Wxo0bdfz4cc2ePduzLDs7m4IOALhkUNABANCZkvn1119r0KBBuv/++3X//fdr1KhRioqK0rp163TZZZepdu3ants7HA653W5J0qhRo1RcXKyHH35Y9913n/bt2+e5TpLq1q0rSSouLlb9+vU9x4pL0uHDhz0z1CWKi4vlcDjKLHO73SoqKqrWYywZx/79+9WrVy/17NlTt9xyi7p06aI1a9aUu06tWrU8l10ul2bPnq3rr79eknTixAk5HA598cUXZR5vcHDlHy/69OmjtLQ0NWjQQKdPn1b79u11+vRpRUdH68EHH9Stt96q7t27a/Xq1WVypbK/d+nMngElY3O73XrnnXcUFhYm6czJ6kJCQrz99QAAcNFxFncAACRdfvnleu2117Rp0ybPskOHDiknJ0c33XRTpeuuXbtWTz31lGem9ptvvvEct17az3/+c4WGhnoK+r59+xQVFaXNmzeXud11113nOa5bkg4cOKBVq1bprrvuqtZjLLF582ZdfvnlGjZsmO6++25POS8uLlaHDh20fPly5efnKz8/X3//+9896919991avHix3G63CgoKNHToUL311lu67bbbtG3bNmVnZ0s6c5K6ynTr1k0bNmzQ0qVL1a9fP0nSjh07lJOToxEjRqhjx47asGGDCgoK5HK5yqx72WWXeX5fBw4cUEZGhiTJ6XSqTZs2ev311yWd2XjQp08f607wBwBAZZhBBwBAZ8rzvHnzNGvWLO3fv18hISGqX7++Jk+erOuuu06HDh2qcN2RI0fqqaeeUt26deV0OnXbbbdp586d59yuTp06SklJ0csvv6w///nPKioq0h//+EfdcsstZW5Xu3ZtpaSkaNKkSZozZ46Ki4v11FNP6c477zTyWH/9618rLS1NXbp0kcPh0O23367LL79cO3bsUO/evbVz505FRUWpQYMGuuaaazzrvfDCC3r55ZfVtWtXFRYW6q677tKgQYNUu3ZtzZgxQ6NHj1bt2rV12223VXr/TqdTDz30kD788EONHTtWktSyZUvdd999evjhh1WnTh3PLvM7duwoc3b32NhYjR49Wp07d1azZs3K/E5mzJihiRMnqmvXriooKFBUVJQeffRRI78zAAAuBIf77H3HAAAAAADABccu7gAAAAAAWICCDgAAAACABSjoAAAAAABYgIIOAAAAAIAFLspZ3DMzM6v8XtL8/Hyj311qMs/WLNN5gZBlOs/WLNN5tmaZzguELNN5tmaZzrM1y3ReIGSZzrM1y3ReIGSZzrM1y3SerVmm8wIhy3SerVmm87zNys/PV5s2bc69wn0RbNmyxchtTN/npZ5lOi8Qskzn2ZplOs/WLNN5gZBlOs/WLNN5tmaZzguELNN5tmaZzguELNN5tmaZzrM1y3ReIGSZzrM1y3Set1kV3Y5d3AEAAAAAsAAFHQAAAAAAC1DQAQAAAACwAAUdAAAAAAALUNABAAAAALAABR0AAAAAAAtQ0AEAAAAAsAAFHQAAAAAAC1DQAQAAAACwAAUdAAAAAAALUNABAAAAALAABR0AAAAAAAtQ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2LHDSM6nn36qJ554wq91T58+Xe7y//3vf9UZUhmHDh3yeZ1JkyYZu/8SbrdbGRkZ+uCDD/TFF1+ouLhYW7du9fnvt1evXsrMzDxn+ZNPPun32AoKCvTTTz/5vb4kffzxx4qLi1OTJk3UoUMH1atXT88884xWr17td2ZqaqqKiookSZs2bdLbb79drTGWdvToUf3pT3/yeb2pU6d6Lj/++OOey3/961+NjEvy/zk1fPhwz2VTr7GmLFu27Jxl+fn5euGFF3zO2rt3b4X/fDVy5EiNGjVKI0eO9Fzu3r27OnXq5HPW9u3b1atXL/34449q1KiRtm7dqh49eujHH3/0Ocu0kveQkn//+Mc//H7vLM+XX36pZ555Rr/97W+NZfpq1qxZmj59ukaMGKGZM2cay/3iiy8kSdOmTdO4ceMUFxenY8eO+ZRx6NChCv/5480339T27dv9WrciBQUF2rt3rwoKCozmng/+vL65XC6tWbNGGzZsOA8jOmPRokVG82JjY31eZ+3atRX+80fJZ5fly5drw4YNfnWB82HBggWey+vXr/dcfvHFF33OMv1++fjjj5/zmWr9+vWKiYnxOav02I4ePeq5/M477/g1tp9++klpaWlauHCh/va3v3m6hilFRUVauXKlX+v+8MMPnsu7du3S999/7/c4TPeLKr9m7WLyZ7a5d+/ensubNm3ShAkT1K9fP7/LxFdffaUXX3xRV199taKiojR16lSFhYWpZ8+eGjx4sF+Zpqxbt06ff/65Jk+erAYNGlQra/Xq1UpJSdH777+vefPm6Z577tGWLVt06tQp9evXz8h4n332WZ/3iChx7NgxLVu2TO+++65atGjh14uOJPXv31/Tp0/X9ddf71n23nvvae7cuVqzZo1PWd9++63mzJmj8PBwjR07VpdffrmWLFmi+fPn69///rdPWdV5USjP4cOH9Yc//EHXXnutmjZtqk8//VSvvPKKrrjiCk2bNs2nrKNHj2rs2LEaNGiQevTo4Vl+6tQpn8d17NgxxcfH69tvv9XPfvYzHT58WO3bt1d8fLzPs35//etf9dZbb6lu3bqeZdHR0Ro6dKhfM8tz5szR1q1b9eijjyo4OFhXXXWVFi9erCNHjuipp57yOa/Ef//7Xy1ZskRr165V586dfV6/9IeTko0HZy/3h4nnVOk377/85S+69957qzWmw4cPa/78+br66qt1zz33aOjQoSoqKtJLL72kDh06+JS1ceNGbdq0SYmJiQoJCdH333+vUaNG6Z577vF5XCNHjpTD4fD8zh0Oh3bs2KGTJ09q8+bNPmWV3rBaUFCg5ORknTp1SgsXLvR5XK+88opeffVVtWrVyrMsKipK06ZN0/z5833O69ixo+d91+FwKDQ0VL/85S81evRoNWzY0Kes0h98pDMfXlJSUjRgwAC/X79Pnz6tDz74QG+//bYOHTqkCRMm6NVXX/U5Z9euXZo6dapmz56tr7/+WiNGjFDdunU1bdo0tW3b1uuc2rVre95L5syZ4/M4ypOSkqKtW7fqrrvu0qZNm/TMM89o06ZNWrBggcaOHet1zlVXXaWoqCiFhoYaGdexY8c0YcIEHTlyRLfffrs6dOigO++8s8zrr7cKCws1ZcoUff7552rUqJEOHjyoe++9V3Fxcapdu7bPebGxseX+3T7xxBM+zwa///77mjlzpkJDQ5WcnKzmzZtr/Pjx+vHHH31+fUtISNDJkyd1+vRpffvttxo4cKBP63vDdHn1J6+ycnT33Xf7lFXy2eWaa65Rs2bN9Omnn2rq1KlasGCBGjdu7PPYSr+mSSrzGv7JJ5/4lLVu3Tr94Q9/kCS99tprat++vST/CpiJ98vSBg8erN/97neKj4/XrbfeqtmzZ2v16tWV7lXszdj++Mc/eiYD/v73v5fpWN748ssvlZCQoM6dO6thw4aez81TpkzRLbfc4vPYSjt48KDeeecdpaWlKSIiQo888ohP669atUozZ85UWlqa6tevr0OHDmncuHF67rnn/PoMabJfSJYU9FdfffWcMu52u3XgwAG/8goLCzVz5kytX79er776arm7h3prypQpmjNnjo4fP67f//73Wr16terXr6/Y2FifC/q2bdv07LPPlnudPx8ykpOTtXLlSg0YMEBjxozx+YWwtDfffFN/+ctfJEk/+9nP9Oyzz+rkyZN6/PHHjRV0f174N2/erCVLluirr77Sww8/rKuuusozTn9MmTJFo0aN0sCBA/XQQw8pPj5eBw4c8GumdMKECRo1apT27t2rWbNm6fTp0zp48KCWLFnic9aBAwcq3CPCn90Rp06dqtGjR3veQKQzszFbt271+YP2VVddpVmzZmn48OH69ttvFR8fr6CgIL82oE2ePFkPPfSQkpOTPcuWLVumxMREnzccBAcHn/Ph0Ol0qlatWj6PS5LS09P17rvveh5Xs2bNNGvWLPXu3dvngl5QUKCVK1dqyZIlqlOnjnJycvTJJ5/49WG59O+5osu+MP2cKmHig+KYMWPUpUsXHT9+XP369dOsWbPUpEkTjR071ueCPm3aNC1ZskT9+vVTp06dtGzZMiUkJOjXv/61z+MyWapLZGdn6/nnn1f79u313nvvqU6dOj5n5OTklCnnknTzzTfr+PHjfo3pH//4R5mfT506pc8//1zjx4/Xa6+95lNWee91+fn5io2N9augT5w4UV9++aUefPBBzZs3TxMnTlRUVJTPOdKZ16GYmBgFBwdr6tSpmjZtmm644QaNHj1ab775pl+ZLpfLr/XOtn79ei1evFiSFBISog4dOuiuu+4qs3HUG999950WLFigX//61+rVq5ciIiKqNa5nnnlG0pm//6+//lobN27U66+/rqCgIL3xxhs+Zc2bN08NGzb0FKTi4mLNmzdPs2fP1ujRo30e20svvVTm59OnTys9PV0vvviiz+8rr7/+ulauXKlDhw5p6tSpOnjwoB544AHNmDHD53Ft27ZNS5cuVWFhoQYPHnxeCrrpwyb9yZsyZYqx+y/vs0t6erqmTJmiWbNm+ZzXsWNHbd68WXfddZceffRRvw4jKlH6Pa6673dut1uFhYXl5vjzXnD33Xdr4cKFevbZZ3XixAm1bdtWaWlpfh2uUNHj9Ocxz5s3T0uWLCnzuXPgwIEaM2aM53XOVxkZGXrrrbeUlZWloKAgpaamqkmTJj7nLFq0SKmpqapfv74kqV27dlq6dKnfkzwm+4VkSUG/7rrryl0+atQon7O2bNmicePGqUOHDlq2bJlfW2NLCw0N9RxXFhER4fkj8+eDduPGjY0f+/XII4+oVatW6tWrV5kx+bprkcvl0mWXXSZJuv322yVJ9evXN3oskr97RDzxxBP629/+pjp16lR7r4WWLVtqyZIlGj58uKZMmaI+ffpo+vTpfo0tLCzMs1Fk3rx5euyxxzRjxgy/sgoLC/3e9bA8+/fvL/MGJ52Z7fT3g3vDhg31xhtvaNKkSYqNjfV7tmjXrl3q2rVrmWU9evTQ3/72N5+zKvo9+/tBuW7duudk1q5dW/Xq1fM5q2PHjoqKitKMGTN07bXXatCgQX7PZJV+Iz/7sj9MP6dKf8gofdmfDxn5+fnq2bOnpDNlseRv2J9ZOunMbPLq1as1f/58Pf30036V89JMlGqXy6X58+drxYoVmjhxYrVmESr6G/D30KSzH0+dOnX02GOP6d133/Ur72whISF+vyf/5z//0c0336zWrVurefPm1SomBQUFeuCBB3T06FHt37/f83fh62tHyYbVkgmF0htyqvNeX7KRseSwuFq1ank+RHrrhRde0JgxY/TJJ59o1qxZOnHihLp3766oqCi/39cLCgr05ZdfKj09XZs3b1Z4eLjuuusun3M2bNhQ5kNrrVq1NHz4cM9z31flfYb8xS9+4fNsnyQ1aNBA4eHhCg8P1w8//KCEhAS/ZzqDg898xK5du3a1N+BUNAHj66EPJUxOjP3jH//QlClTFBoaqunTp+tXv/qVX2OSyv/scs899yglJcWvvPHjx8vlcmnt2rVKSUnR8ePH9eCDD+rhhx/2+fXbxAbyEt988426dOlS5txUJZd9ndkvkZmZqUOHDqlNmzbauXOnTp486ddz3eSkgNvtPmdSyJ89IUr89re/1XXXXafevXvrzjvv1JAhQ/wq59KZ97ez9z5u2LChQkJC/Moz2S8kSwr6o48+qvT0dNWtW1d33HFHtbJ69uypevXqaePGjZ5jaUr+6P05fqL0L7bkxbYk01f169f3lF9T0tLS9Nprr2n8+PF67LHH/M7Jz8/3XC7ZUi7590Fv1KhR5b7w+3P84ZIlS5SWlqaoqCg99NBDFR7j4YvU1FTt3btXvXv31po1a9SlSxe1bNnS55zSM7WNGzfWyJEj/R7T1VdfbfQkhqV3hS4xZcoUvz6wlPytBwcHKyEhQe+995769Onj17gq+nDuzwtYeXukuN3uc3at9VZoaKh27dql5s2be5bt2rXLr7ENGDBAK1as0J49exQTE1Otre179uzxvJFL8uwm7++LvsnnVMnYpDO/+9IfOPz5kFH6OVV6w4g/r0MbN27UCy+8oP79+ys5OVkvvPCCRo0apcTERJ8PpzBZqnv16qW9e/dq0KBB2rZtm7Zt21bmOl9ERER49hIosXTpUt18881+j688pd8fquPQoUN+fzvL8uXL9dVXX2nZsmWaOnWq57leendCX61fv1533nmnpDP/xydPnvRp/a5du3o2rJa+XB2FhYUqKChQnTp1PLM4BQUFfj0HateurS5duqhL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\n",
      "text/plain": [
       "<Figure size 1008x432 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with pd.HDFStore(itch_store) as store:\n",
    "    stocks = store['R'].loc[:, ['stock_locate', 'stock']]\n",
    "    trades = store['P'].append(store['Q'].rename(columns={'cross_price': 'price'}), sort=False).merge(stocks)\n",
    "\n",
    "trades['value'] = trades.shares.mul(trades.price)\n",
    "trades['value_share'] = trades.value.div(trades.value.sum())\n",
    "\n",
    "trade_summary = trades.groupby('stock').value_share.sum().sort_values(ascending=False)\n",
    "trade_summary.iloc[:50].plot.bar(figsize=(14, 6), color='darkblue', title='Share of Traded Value')\n",
    "\n",
    "plt.gca().yaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y)))\n",
    "sns.despine()\n",
    "plt.tight_layout()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": true,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "282px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}